import { useState } from 'react';
import { NextSeo } from 'next-seo';

<NextSeo description="Calculate the costs of running your PHP application on AWS Lambda." />

# Costs of a serverless application

Unlike traditional hosting, serverless hosting is billed based on usage. This means that you only pay for what you use, **down to the request**.

To be clear, this means that if your application is not used, you don't pay anything.

On AWS Lambda, you pay for:

- the number of requests
- the duration of the requests (the time it takes to execute your code)

There are no costs when the PHP application is waiting between requests (or jobs, events, etc.). It doesn't matter if AWS Lambda scaled your functions up to several instances (containers), you only pay for the requests and the duration of the requests.

For some use cases it has interesting consequences: 1 job running for 10 minutes has about the same costs as 600 jobs running for 1 second in parallel.

## Costs calculator

Use the calculator below to estimate the costs of running your PHP application serverless on AWS Lambda.

<Calculator />

export function Calculator() {
    const lambdaFreeTierRequests = 1000000;
    const lambdaFreeTierDuration = 400000;
    const lambdaCostPerRequest = 0.2 / 1000000;
    const lambdaCostPerGbS = 0.0000133334; // ARM

    const apiGatewayFreeTierRequests = 1000000;
    const apiGatewayCostPerRequest = 1 / 1000000;

    const cloudfrontFreeTierRequests = 10000000;
    const cloudfrontFreeTierBandwidth = 1000;
    const cloudfrontCostPerRequest = 0.012 / 10000;
    const cloudfrontCostPerGb = 0.085;

    const s3FreeRequests = 20000;
    const s3CostPerRequest = 0.0004 / 1000;

    const sqsFreeTierRequests = 1000000;
    const sqsCostPerRequest = 0.4 / 1000000;

    const rdsStorageFreeTier = 20;
    const rdsStorageCostPerGb = 0.115;
    const rdsInstanceCost = {
        'db.t4g.micro': 0.016,
        'db.t4g.small': 0.032,
        'db.t4g.medium': 0.065,
        'db.t4g.large': 0.129,
        'db.t4g.xlarge': 0.258,
        'db.t4g.2xlarge': 0.517,
    };
    const rdsInstanceDescription = {
        'db.t4g.micro': '1 vCPU, 1GB RAM',
        'db.t4g.small': '1 vCPU, 2GB RAM',
        'db.t4g.medium': '2 vCPU, 4GB RAM',
        'db.t4g.large': '2 vCPU, 8GB RAM',
        'db.t4g.xlarge': '4 vCPU, 16GB RAM',
        'db.t4g.2xlarge': '8 vCPU, 32GB RAM',
    };
    const natGatewayCost = 0.045 * 24 * 30;
    const natInstanceCost = 0.0042 * 24 * 30;

    const [ httpRequests, setHttpRequests ] = useState(100000);
    const [ httpDuration, setHttpDuration ] = useState(100);
    const [ assetsRequests, setAssetsRequests ] = useState(0);
    const [ bandwidth, setBandwidth ] = useState(0);
    const [ cacheHit, setCacheHit ] = useState(90);
    const [ jobs, setJobs ] = useState(0);
    const [ database, setDatabase ] = useState(undefined);
    const [ databaseSize, setDatabaseSize ] = useState(20);
    const [ natType, setNatType ] = useState('natInstance');
    const [ jobDuration, setJobDuration ] = useState(50);

    // Calculate costs
    const lambdaInvocations = Number(httpRequests) + Number(jobs);
    const lambdaGbS = httpRequests * httpDuration / 1000 + jobs * jobDuration / 1000;
    const lambdaCost = Math.max(0, (lambdaInvocations - lambdaFreeTierRequests) * lambdaCostPerRequest)
        + Math.max(0, (lambdaGbS - lambdaFreeTierDuration) * lambdaCostPerGbS);
    const apiGatewayCost = Math.max(0, (httpRequests - apiGatewayFreeTierRequests) * apiGatewayCostPerRequest);
    const cloudfrontRequestsCost = Math.max(0, (assetsRequests - cloudfrontFreeTierRequests) * cloudfrontCostPerRequest);
    const cloudfrontBandwidthCost = Math.max(0, (bandwidth - cloudfrontFreeTierBandwidth) * cloudfrontCostPerGb);
    const cloudfrontCost = cloudfrontRequestsCost + cloudfrontBandwidthCost;
    const requestsHittingS3 = assetsRequests * cacheHit / 100;
    const s3Cost = Math.max(0, (requestsHittingS3 - s3FreeRequests) * s3CostPerRequest);
    const numberOfSqsRequestsPerJob = 3; // Send, receive, delete
    const sqsCost = Math.max(0, (jobs * numberOfSqsRequestsPerJob - sqsFreeTierRequests) * sqsCostPerRequest);
    const rdsCost = database ? rdsInstanceCost[database] * 24 * 30 + Math.max(0, (databaseSize - rdsStorageFreeTier) * rdsStorageCostPerGb) : 0;
    const natCost = (database && natType) ? (natType === 'natInstance' ? natInstanceCost : natGatewayCost) : 0;

    const totalCost = lambdaCost + apiGatewayCost + cloudfrontCost + s3Cost + sqsCost + rdsCost + natCost;

    function smallApi() {
        setHttpRequests(100000);
        setHttpDuration(100);
        setAssetsRequests(0);
        setBandwidth(0);
    }
    function largeApi() {
        setHttpRequests(5000000);
        setHttpDuration(100);
        setAssetsRequests(0);
        setBandwidth(0);
    }
    function smallWebsite() {
        setHttpRequests(100000);
        setHttpDuration(200);
        setAssetsRequests(100000);
        setBandwidth(2);
        setCacheHit(90);
    }
    function largeWebsite() {
        setHttpRequests(5000000);
        setHttpDuration(200);
        setAssetsRequests(5000000);
        setBandwidth(100);
        setCacheHit(90);
    }

    return (
        <div className="mt-6">
            <Section title="Presets">
                <div className="mt-4 grid grid-cols-4 gap-4">
                    <Button onClick={smallApi}>Small API</Button>
                    <Button onClick={largeApi}>Large API</Button>
                    <Button onClick={smallWebsite}>Small website</Button>
                    <Button onClick={largeWebsite}>Large website</Button>
                </div>
            </Section>

            <Section title="HTTP backend (assets excluded)">
                <Field title="HTTP requests per month">
                    <InputField type="number" value={httpRequests} valueSetter={setHttpRequests} />
                </Field>
                <Field title="Average execution time (ms)">
                    <InputField type="number" value={httpDuration} valueSetter={setHttpDuration} />
                </Field>
            </Section>

            <Section title="Website assets">
                <Field title="Asset requests per month (backend excluded)">
                    <InputField type="number" value={assetsRequests} valueSetter={setAssetsRequests} />
                </Field>
                <Field title="Total bandwidth (backend included, in GB)">
                    <InputField type="number" value={bandwidth} valueSetter={setBandwidth} />
                </Field>
                <Field title="CDN cache hit rate (%)">
                    <InputField type="number" value={cacheHit} valueSetter={setCacheHit} min="0" max="100" />
                </Field>
            </Section>

            <Section title="Asynchronous jobs">
                <Field title="Jobs per month">
                    <InputField type="number" value={jobs} valueSetter={setJobs} />
                </Field>
                <Field title="Average execution time per job (ms)">
                    <InputField type="number" value={jobDuration} valueSetter={setJobDuration} />
                </Field>
            </Section>

            <Section title="Database (MySQL or PostgreSQL)">
                <Field title="Instance size">
                    <SelectField value={database} valueSetter={setDatabase}>
                        <option value="">None</option>
                        {Object.entries(rdsInstanceDescription)
                            .map(([instance, description]) => <option value={instance}>{description} ({instance})</option>)}
                    </SelectField>
                </Field>
                {database && <Field title="Database size (storage in GB)">
                    <InputField type="number" value={databaseSize} valueSetter={setDatabaseSize} />
                </Field>}
                {database && <Field title="NAT Gateway type">
                    <SelectField value={natType} valueSetter={setNatType}>
                        <option value="natInstance">NAT instance (for smaller applications)</option>
                        <option value="natGateway">Managed NAT Gateway (intensive production workloads)</option>
                        <option value="">None (database outside a VPC)</option>
                    </SelectField>
                </Field>}
            </Section>

            <Section title="Monthly costs (free tier included)">
                <Field title="AWS Lambda costs">
                    <PlainTextField>${lambdaCost.toFixed(2)}</PlainTextField>
                </Field>
                <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    Assumptions: 1024MB of memory (Bref default config) and ARM CPUs.
                </div>
                <Field title="API Gateway costs">
                    <PlainTextField>${apiGatewayCost.toFixed(2)}</PlainTextField>
                </Field>
                <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    Assumptions: API Gateway v2. Lambda Function URL could be used instead of API Gateway to reduce costs.
                </div>
                {assetsRequests > 0 && <Field title="CloudFront CDN costs">
                    <PlainTextField>${cloudfrontCost.toFixed(2)}</PlainTextField>
                </Field>}
                {assetsRequests > 0 && <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    CloudFront costs can vary depending on the use case (e.g. number and size of assets, serving images, streaming videos…).
                    Browser caching can be used to reduce costs on high traffic websites.
                    Cloudflare can also be considered as an alternative to CloudFront.
                </div>}
                {assetsRequests > 0 && <Field title="S3 costs">
                    <PlainTextField>${s3Cost.toFixed(2)}</PlainTextField>
                </Field>}
                {assetsRequests > 0 && <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    S3 is used to store assets.
                </div>}
                {jobs > 0 && <Field title="SQS costs">
                    <PlainTextField>${sqsCost.toFixed(2)}</PlainTextField>
                </Field>}
                {jobs > 0 && <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    Assumptions: Standard SQS queue.
                </div>}
                {database && <Field title="Database costs">
                    <PlainTextField>${rdsCost.toFixed(2)}</PlainTextField>
                </Field>}
                {database && <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    Costs vary depending on the use case. Aurora Serverless can be considered, as well as reserved RDS instances for lower costs.
                    {' '}
                    <a href="/docs/environment/database-planetscale">PlanetScale</a> can also be considered as an alternative to RDS.
                </div>}
                {database && <Field title="NAT Gateway costs">
                    <PlainTextField>${natCost.toFixed(2)}</PlainTextField>
                </Field>}
                {database && <div className="text-xs text-gray-500 pb-1.5 sm:pb-2">
                    NAT Gateway is necessary when using a database in a private VPC (virtual network).
                    Data transfer cost is excluded.
                </div>}
            </Section>

            <div className="mt-6 sm:grid sm:grid-cols-5 sm:items-start sm:gap-4 sm:py-2">
                <div className="block sm:col-span-2 text-xl font-extrabold leading-6 sm:pt-1.5">
                    Total AWS costs
                </div>
                <div className="mt-2 sm:col-span-3 sm:mt-0 py-1.5 sm:leading-6 text-xl font-extrabold">
                    ${totalCost.toFixed(2)}/month
                </div>
            </div>
            <div className="mt-4 text-xs text-gray-500 pb-1.5 sm:pb-2">
                <div>
                    The calculator takes into account the AWS free tier (except the free tier that expires after 12 months).
                    It uses prices for the us-east-1 region. Other regions can have slightly higher costs (a few percents usually).
                </div>
                <div className="mt-1">
                    Costs can vary at scale and can be optimized in numerous ways.
                    For a cost-optimized architecture tailored to your needs, <a href="mailto:matthieu@bref.sh">get in touch</a>.
                </div>
            </div>
        </div>
    )
}

export function Section({ children, title, ...props }) {
    return <>
        <h2 className="mt-8 text-base font-bold leading-7 pb-2 border-b border-gray-900/10">
            {title}
        </h2>
        <div className="mt-2 space-y-8 pb-12 sm:space-y-0 sm:pb-0">
            {children}
        </div>
    </>
}

export function Field({ children, title, ...props }) {
    return <div className="sm:grid sm:grid-cols-5 sm:items-start sm:gap-4 sm:py-2">
        <label className="block sm:col-span-2 text-sm font-medium leading-6 sm:pt-1.5">
            {title}
        </label>
        <div className="mt-2 sm:col-span-3 sm:mt-0">
            {children}
        </div>
    </div>
}

export function Button({ children, ...props }) {
    return <button
        className="inline-flex justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
        {...props}
    >
        {children}
    </button>
}

export function InputField({ children, value, valueSetter, ...props }) {
    return <input {...props} className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6"
                  value={value}
                  onChange={e => valueSetter(e.target.value)}/>
}

export function SelectField({ children, value, valueSetter, ...props }) {
    return <select type="text" {...props} className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6"
                  value={value}
                  onChange={e => valueSetter(e.target.value)}>{children}</select>;
}

export function PlainTextField({ children, ...props }) {
    return <div {...props} className="py-1.5 sm:text-sm sm:leading-6">
        {children}
    </div>
}
